package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.heima.article.mapper.ApArticleConfigMapper;
import com.heima.article.mapper.ApArticleContentMapper;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.ApArticleService;
import com.heima.article.service.ArticleFreemarkerService;
import com.heima.common.constants.ArticleConstants;
import com.heima.common.redis.CacheService;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.pojos.ApArticleConfig;
import com.heima.model.article.pojos.ApArticleContent;
import com.heima.model.article.vos.HotArticleVo;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.mess.ArticleVisitStreamMess;
import freemarker.template.TemplateException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class ApArticleServiceImpl implements ApArticleService {

    @Autowired
    private ApArticleMapper apArticleMapper;

    @Autowired
    private ApArticleContentMapper contentMapper;

    @Autowired
    private ApArticleConfigMapper configMapper;

    @Autowired
    private ArticleFreemarkerService freemarkerService;

    @Autowired
    private CacheService cacheService;

    /**
     * 更加条件查询文章的列表数据
     * dto: 传递的查询参数
     * type: 查询的类型 1 查询更多 2 查询更新
     *
     * @param dto
     * @param type
     */
    @Override
    public ResponseResult load(ArticleHomeDto dto, byte type) {

        List<ApArticle> list = apArticleMapper.load(dto, type);

        return ResponseResult.okResult(list);
    }

    /**
     * 加载文章列表
     *
     * @param dto
     * @param type      1 加载更多   2 加载最新
     * @param firstPage true 是首页  false 不是首页
     * @return
     */
    @Override
    public ResponseResult loadArticleListV2(ArticleHomeDto dto, byte type, boolean firstPage) {
        //1. 判断firstPage是否为true 如果为true则冲redis查询数据 否则从数据库查询
        if(firstPage){
            String key = ArticleConstants.HOT_ARTICLE_FIRST_PAGE + dto.getTag();
            String json = cacheService.get(key);
            if(StringUtils.isNotBlank(json)){
                List<HotArticleVo> voList = JSON.parseArray(json, HotArticleVo.class);

                return ResponseResult.okResult(voList);
            }

        }

        return load(dto, type);

    }

    /**
     * 保存app端相关文章
     *
     * @param dto
     * @return
     */
    @Override
    public ResponseResult saveArticle(ArticleDto dto) throws IOException, TemplateException {
        //1. 检查参数
        if(dto == null){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        //2. 判断dto中是否包含id 如果包含走更新如果没有走新增
        ApArticle apArticle = new ApArticle();
        BeanUtils.copyProperties(dto, apArticle);
        if(null ==dto.getId()){
            //2.1 新增一个文章 - 新增三张表

            apArticleMapper.insert(apArticle);

            ApArticleContent content = new ApArticleContent();
            content.setContent(dto.getContent());
            content.setArticleId(apArticle.getId());
            contentMapper.insert(content);

            ApArticleConfig config = new ApArticleConfig();
            config.setArticleId(apArticle.getId());
            config.setIsDown(Boolean.FALSE);
            config.setIsForward(Boolean.TRUE);
            config.setIsDelete(Boolean.FALSE);
            config.setIsComment(Boolean.TRUE);
            configMapper.insert(config);

        }else {
            //2.2 更新文章 - 更新2张表
            apArticleMapper.updateById(apArticle);

            ApArticleContent content = contentMapper
                    .selectOne(Wrappers.<ApArticleContent>lambdaQuery()
                            .eq(ApArticleContent::getArticleId, apArticle.getId()));

            content.setContent(dto.getContent());
            contentMapper.updateById(content);
        }

        //生成静态页面-使用异步的方式来生成
        freemarkerService.buildArticleToMinIO(apArticle.getId());

        //3. 封装返回结果
        return ResponseResult.okResult(apArticle.getId());
    }

    /**
     * 根据时时统计的结果到数据库 同时更新热点文章的缓存数据
     *
     * @param streamMess
     */
    @Override
    public void updateScore(ArticleVisitStreamMess streamMess) {
        //1. 更新统计结果到数据库
        ApArticle apArticle = updateDB(streamMess);

        //1.1 重新计算文章的得分
        Integer score = computeScore(apArticle);
        score *= 3;

        //2. 更新频道的缓存数据
        updateCacheByTag(apArticle, score, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + apArticle.getChannelId());

        //3. 更新推荐的缓存数据
        updateCacheByTag(apArticle, score, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG);
    }

    /**
     * 更新缓存中的数据
     * @param apArticle 文章对象
     * @param score 某一个文章的最终得分
     * @param key redis 中的key值
     */
    private void updateCacheByTag(ApArticle apArticle, Integer score, String key) {
        String json = cacheService.get(key);
        if(StringUtils.isNotBlank(json)){
            List<HotArticleVo> voList = JSON.parseArray(json, HotArticleVo.class);

            //1. 判断一下当前要更新的文章是否再voList集合中
            Boolean flag = Boolean.FALSE;
            for (HotArticleVo vo : voList) {
                if(apArticle.getId().equals(vo.getId())){
                    vo.setScore(score);
                    flag = Boolean.TRUE;
                    break;
                }
            }

            //2. 如果当前更新的文章不在voList里面 那么就需要继续判断
            if(!flag){
                HotArticleVo vo = new HotArticleVo();
                BeanUtils.copyProperties(apArticle, vo);
                vo.setScore(score);

                if(voList.size() < 30){
                    //3. 判断如果voList的长度不足30个，直接添加最新的文章到voList结合即可
                    voList.add(vo);
                }else{
                    //4. 判断如果voList的长度已经大于等于30个,那么就获取voList中得分最低的元素跟当前的文章进行比较 如果当前的文章得分比最小得分大 那么就替换voList中的数据
                    voList = voList.stream()
                            .sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                            .collect(Collectors.toList());

                    HotArticleVo last = voList.get(voList.size() - 1);
                    if(score > last.getScore()){
                        voList.remove(last);
                        voList.add(vo);
                    }
                }

            }

            //5. 重新对voList进行排序
            voList = voList.stream()
                    .sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                    .collect(Collectors.toList());

            //6. 把voList集合写入到redis
            cacheService.set(key, JSON.toJSONString(voList));
        }

    }

    private ApArticle updateDB(ArticleVisitStreamMess streamMess) {
        ApArticle apArticle = apArticleMapper.selectById(streamMess.getArticleId());

        apArticle.setCollection((apArticle.getCollection() == null ? 0 : apArticle.getCollection()) + streamMess.getCollect());
        apArticle.setViews((apArticle.getViews() == null ? 0:apArticle.getViews()) + streamMess.getView());
        apArticle.setLikes((apArticle.getLikes() == null ? 0:apArticle.getLikes()) + streamMess.getLike());
        apArticle.setComment((apArticle.getComment() == null ? 0: apArticle.getComment()) + streamMess.getComment());

        apArticleMapper.updateById(apArticle);

        return apArticle;
    }

    /**
     * 计算一篇文章的得分
     * @param apArticle
     * @return
     */
    private Integer computeScore(ApArticle apArticle) {
        Integer score = 0;

        if(apArticle.getLikes() != null){
            score += apArticle.getLikes() * ArticleConstants.HOT_ARTICLE_LIKE_WEIGHT;
        }

        if(apArticle.getViews() != null){
            score += apArticle.getViews();
        }

        if(apArticle.getComment() != null){
            score += apArticle.getComment() * ArticleConstants.HOT_ARTICLE_COMMENT_WEIGHT;
        }

        if(apArticle.getCollection() != null){
            score += apArticle.getCollection() * ArticleConstants.HOT_ARTICLE_COLLECTION_WEIGHT;
        }

        return score;
    }
}
